﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.IO;

namespace Pittypat
{
    /// <summary>
    /// 表示一个可以动态添加属性的对象，属性名称不区分大小写，属性值的类型为 <see cref="IJson"/>。
    /// </summary>
    /// <remarks>
    /// 该类型的对象实例同时可以方便地编写为 JSON 字符串。FunctionModel.OutArgs 返回的就是该类型的对象实例。
    /// </remarks>
    public sealed class DynamicModel : DynamicObject, IComplex
    {
        // 属性字典
        private Dictionary<string, IJson> properties;

        /// <summary>
        /// 初始化 <see cref="DynamicModel"/> 类的新实例。
        /// </summary>
        public DynamicModel()
        {
            this.properties = new Dictionary<string, IJson>(StringComparer.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// 获取或者设置指定名称的属性值。
        /// </summary>
        /// <param name="name">属性名称，不区分大小写。如果不是有效的标识符，则返回 null，设置时则不产生任何动作，也不会产生异常。</param>
        /// <returns>属性值，如果指定名称的属性不存在，则返回 null。</returns>
        public IJson this[string name]
        {
            get
            {
                if (name.IsIdentifier())
                {
                    IJson json = null;

                    if (this.properties.TryGetValue(name, out json))
                    {
                        return json;
                    }
                }
                
                return null;
            }
            set
            {
                if (name.IsIdentifier())
                {
                    SetProperty(name, value);
                }
            }
        }
        
        /// <summary>
        /// 设置指定名称的属性值。
        /// </summary>
        /// <param name="name">属性名称。</param>
        /// <param name="value">属性值。DBNull 与 null 等价，并被设置为 null。属性值可以是 IJson 或可空的 IJson 值。</param>
        /// <returns>如果属性值是 null、DBNull、IJson 或可空的 IJson，返回 true，否则返回 false。</returns>
        internal bool SetProperty(string name, object value)
        {
            if (value.IsNullOrDBNull())
            {
                this.properties[name] = null;
                return true;
            }

            if (value is IJson)
            {
                this.properties[name] = (IJson)value;
                return true;
            }

            var type = value.GetType();
            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>) &&
                typeof(IJson).IsAssignableFrom(type.GetGenericArguments()[0]))
            {
                dynamic d = value;
                this.properties[name] = d.Value;

                return true;
            }

            // 不接受其他类型的值
            return false;
        }

        /// <summary>
        /// 尝试获取指定的属性值。
        /// </summary>
        /// <param name="binder">执行动态操作需要的绑定信息。</param>
        /// <param name="result">操作的结果，为 null，或者一个 IJson 值。</param>
        /// <returns>如果操作成功，返回 true，否则返回 false。</returns>
        public override bool TryGetMember(
            GetMemberBinder binder, out object result)
        {
            IJson json = null;

            if (this.properties.TryGetValue(binder.Name, out json))
            {
                result = json;
                return true;
            }

            result = null;
            return false;
        }

        /// <summary>
        /// 尝试设置指定的属性值。
        /// </summary>
        /// <param name="binder">执行动态操作需要的绑定信息。</param>
        /// <param name="value">属性值。</param>
        /// <returns>如果操作成功，返回 true，否则返回 false。</returns>
        public override bool TrySetMember(
            SetMemberBinder binder, object value)
        {
            return SetProperty(binder.Name, value);
        }

        /// <summary>
        /// 将当前对象作为 JSON 字符串写入指定的文本流。属性名称总是使用 camel 大小写格式。
        /// </summary>
        /// <param name="writer">目标文本流。</param>
        /// <exception cref="global::System.ArgumentNullException">writer 是 null。</exception>
        public void Write(TextWriter writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            // 属性数量
            int count = this.properties.Count;

            writer.Write('{');

            if (count > 0)
            {
                // 属性名称列表
                var names = this.properties.Keys.ToArray();
                IJson val = null;

                for (int i = 0; i < names.Length; ++i)
                {
                    val = this.properties[names[i]];

                    if (val == null)
                    {
                        writer.Write("\"{0}\":null", names[i].Camel());
                    }
                    else
                    {
                        writer.Write("\"{0}\":", names[i].Camel());
                        val.Write(writer);
                    }

                    if (i < names.Length - 1)
                    {
                        writer.Write(',');
                    }
                }
            }

            writer.Write('}');
        }

        /// <summary>
        /// 返回当前对象的 JSON 字符串。
        /// </summary>
        /// <returns>当前对象的 JSON 字符串。</returns>
        public override string ToString()
        {
            if (this.properties.Count > 0)
            {
                using (var writer = new StringWriter())
                {
                    this.Write(writer);
                    return writer.ToString();
                }
            }

            return "{}";
        }
    }
}
